package com.fishingwithme.domain.spots;

import com.fishingwithme.domain.circle.CircleService;
import com.fishingwithme.domain.common.LikeService;
import com.fishingwithme.domain.common.Location;
import com.fishingwithme.domain.credit.CreditService;
import com.fishingwithme.domain.fisher.FisherSpot;
import cn.fishingwithme.infrastructure.dtos.*;
import cn.fishingwithme.infrastructure.mapper.*;
import com.fishingwithme.infrastructure.dtos.*;
import com.fishingwithme.infrastructure.mapper.*;
import com.fishingwithme.infrastructure.utils.CommonUtils;
import com.fishingwithme.infrastructure.utils.GeocoderResponse;
import com.fishingwithme.infrastructure.utils.IdGenerator;
import com.fishingwithme.infrastructure.utils.TencentGeocoderClient;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.assertj.core.util.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static net.minidev.json.JSONArray.toJSONString;

@Service
public class SpotServiceImpl implements SpotService {
    @Autowired
    private TencentGeocoderClient geocoderClient;

    @Autowired
    private IdGenerator idGenerator;

    @Autowired
    private SpotMapper spotMapper;

    @Autowired
    private SpotPageMapper spotPageMapper;

    @Autowired
    private SpotQueryMapper spotQueryMapper;

    @Autowired
    private SpotVisitorHistoryMapper spotVisitorHistoryMapper;

    @Autowired
    private SpotHavestMapper spotHavestMapper;

    @Autowired
    private CircleService circleService;

    @Autowired
    private CreditService creditService;
    @Autowired
    private LikeService likeService;


    private static final Logger logger = LoggerFactory.getLogger(SpotServiceImpl.class);

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Spot addSpot(SpotDto dto) {
        Long userId = CommonUtils.getUserId();
        try {
            GeocoderResponse reverseResp =
                    CommonUtils.getGeocoder(geocoderClient, dto.getLocation());
            if (reverseResp.getStatus() != 0) {
                throw new RuntimeException("获取定位点失败.");
            }

            Spot spot = Spot.builder()
                    .id(idGenerator.nextId())
                    .cityCode(reverseResp.getResult().getAdInfo().getCityCode())
                    .city(reverseResp.getResult().getAddressComponent().getCity())
                    .longitude(dto.getLocation().getLongitude())
                    .latitude(dto.getLocation().getLatitude())
                    .navLine(toJSONString(dto.getLine()))
                    .name(dto.getName())
                    .isFree(dto.getIsFree())
                    .waterType(dto.getWaterType())
                    .lvl(dto.getLvl())
                    .spotTypes(toJSONString(dto.getSpotTypes()))
                    .fishTypes(toJSONString(dto.getFishTypes()))
                    .isParkingConvenient(dto.getParkingEasy())
                    .distance(dto.getDistance())
                    .pictures(toJSONString(dto.getPics()))
                    .isPrivate(dto.getSecret())
                    .description(dto.getDescription())
                    .build();
            spot.setCreatedBy(userId);
            spot.setUpdatedBy(userId);

            spotMapper.insert(spot);

            FisherSpot fisherSpot = FisherSpot.builder()
                    .spotId(spot.getId())
                    .fisherId(userId)
                    .isCollected(Boolean.FALSE)
                    .build();

            spotMapper.insertFisherSpot(fisherSpot);

            SpotUpdateDto spotUpdate = SpotUpdateDto.builder()
                    .id(idGenerator.nextId())
                    .spotId(spot.getId())
                    .accExpo(0.0)
                    .accRec(0)
                    .accScore(0)
                    .upCnt(1)
                    .accFisher(0)
                    .accZero(0.0)
                    .accNorm(0.0)
                    .accNorm(0.0)
                    .build();
            spotMapper.insertSpotUpdate(spotUpdate);

            if (dto.getCircleId() != null) {
                circleService.addSpotToCircle(dto.getCircleId(), spot.getId());
            }

            creditService.rewardSpotCreateCredit(userId);
            return spot;

        } catch (Exception e) {
            logger.error("获取定位点失败.", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public SpotProps getProps() {
        return SpotProps.builder()
                .lvls(Arrays.asList(SpotLvl.values()))
                .waterTypes(Arrays.asList(WaterType.values()))
                .spotTypes(List.of(SpotType.values()))
                .fishTypes(List.of(FishType.values()))
                .distances(List.of(ParkingDistance.values()))
                .build();
    }

    @Override
    public List<SpotDashboardDto> getAllSpotsByLocation(Location location, Integer distance) {
        try {
            Long userId = CommonUtils.getUserId();
            return spotMapper.selectAllByLocationAndDistance(location.getLatitude(), location.getLongitude(), userId, distance);
        } catch (Exception e) {
            logger.error("获取定位点失败.", e);
            throw new RuntimeException(e);
        }

    }

    @Override
    public SpotDetailDto getSpot(Long spotId) {
        return Optional.ofNullable(spotMapper.selectById(spotId)).map(SpotDetailDto::format).orElse(null);
    }

    @Override
    public UserSpotDto getSpot() {
        return Optional.ofNullable(spotMapper.selectUserLatestSpot(CommonUtils.getUserId())).map(UserSpotDto::format).orElse(null);
    }

    @Override
    public UserSpotDto getUserSpot(Long spotId) {
        Long userId = CommonUtils.getUserId();
        return Optional.ofNullable(spotMapper.selectUserSpot(spotId, userId)).map(UserSpotDto::format).orElse(null);
    }


    @Override
    public List<SpotDashboardDto> selectMySpotsByLocationAndDistance(Location location, Integer distance) {
        Long userId = CommonUtils.getUserId();
        return spotMapper.selectMySpotsByLocationAndDistance(location.getLatitude(), location.getLongitude(), userId, distance);
    }

    @Transactional
    @Override
    public void collectSpot(Long spotId) {
        Long userId = CommonUtils.getUserId();
        SpotDetailDto spot = spotMapper.selectById(spotId);
        if (spot == null || Objects.equals(userId, spot.getCreatedBy())) {
            return;
        }

        Boolean collected = spotMapper.hasCollectedByUser(spotId, userId);
        if (collected) {
            return;
        }
        FisherSpot fisherSpot = FisherSpot.builder()
                .spotId(spotId)
                .fisherId(userId)
                .isCollected(Boolean.TRUE)
                .build();
        /**
         * 是秘密钓点
         */
        if (!spot.getSecret()) {
            Integer creditVal = creditService.transferPrivateSpotCredit(userId, spot
                    .getCreatedBy(), spot.getCreditVal());
            fisherSpot.setCreditValPay(creditVal);
        }

        spotMapper.insertFisherSpot(fisherSpot);


    }

    @Transactional
    @Override
    public VisitHarvestDto addVisitHarvest(VisitHarvestDto dto) {
        Long userId = CommonUtils.getUserId();
        dto.setVisitId(userId);
        dto.setId(idGenerator.nextId());
        dto.format();
        spotMapper.insertVisitHarvest(dto);
        this.updateSpotStatistics(dto);
        creditService.rewardHarvestShowCredit(userId);
        SpotEnhanceDto spot = spotMapper.selectByIdForUpdate(dto.getSpotId());
        dto.setCityCode(spot.getCityCode());
        return dto;
    }

    private void updateSpotStatistics(VisitHarvestDto dto) {
        SpotUpdateDto updateDto = SpotUpdateDto.builder().build();
        updateDto.setSpotId(dto.getSpotId());
        if (HarvestType.BAOHU.equals(dto.getHarvestType())) {
            updateDto.setAccExpo(1.0);
        } else if (HarvestType.KONGJUN.equals(dto.getHarvestType())) {
            updateDto.setAccZero(1.0);
        } else if (HarvestType.NORMAL.equals(dto.getHarvestType())) {
            updateDto.setAccNorm(1.0);
        }
        if (dto.getIsRec()) {
            updateDto.setAccRec(1);
        }
        updateDto.setAccScore(dto.getScore().getType());
        updateDto.setAccFisher(1);
        spotMapper.updateSpotUpdate(updateDto);

    }

    @Override
    public IPage<SpotHarvestDto> getSpotHarvests(Integer pageNum, Integer pageSize, Long spotId) {
        Page<SpotHarvestDto> page = new Page<>(pageNum, pageSize);
        IPage<SpotHarvestDto> pageData = spotHavestMapper.getSpotHarvests(page, spotId);
        List<SpotHarvestDto> harvests = pageData.getRecords();
        pageData.setRecords(harvests.stream().map(SpotHarvestDto::format).collect(Collectors.toList()));
        return pageData;
    }

    @Override
    public SpotNavDto getSpotNavLineById(Long spotId) {
        SpotNavDto dto = spotMapper.getSpotNavLineBySpotId(spotId);
        if (dto == null) {
            return null;
        }
        return dto.format();
    }

    @Override
    public List<SpotQueryResDto> searchSpots(SpotQueryDto dto) {
        Long userId = CommonUtils.getUserId();
        return Optional.ofNullable(spotQueryMapper.searchSpots(dto.getQuery(), userId)).orElse(Lists.newArrayList());
    }

    @Override
    public IPage<SpotQueryResDto> listCollectedSpots(Integer pageNum, Integer pageSize) {
        Page<SpotQueryResDto> page = new Page<>(pageNum, pageSize);
        Long userId = CommonUtils.getUserId();
        return spotQueryMapper.listSpotPages(page, userId);
    }

    @Override
    public IPage<SpotPageDto> getCollectedSpots(Long userId, Boolean isCollected, Integer pageNum, Integer pageSize) {
        try {
            Page<SpotPageDto> page = new Page<>(pageNum, pageSize);
            return spotPageMapper.selectCollectedSpots(page, userId, isCollected);
        } catch (Exception e) {
            logger.error("获取收藏钓点失败.", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public IPage<SpotCollectPageDto> getPublicCollectedSpots(Boolean isCollected, Long spotId, Integer pageNum, Integer pageSize) {
        try {
            Page<SpotCollectPageDto> page = new Page<>(pageNum, pageSize);
            // 使用固定的用户ID或者查询所有公开的收藏钓点
            // 这里假设查询所有被收藏的钓点，不区分用户
            return spotPageMapper.selectCollectedSpotPages(page, isCollected,spotId);
        } catch (Exception e) {
            logger.error("获取公开收藏钓点失败.", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public IPage<SpotVisitHisDto> getSpotVisitHis(Integer pageNum, Integer pageSize, Long spotId) {
        Page<SpotVisitHisDto> page = new Page<>(pageNum, pageSize);
        return spotVisitorHistoryMapper.listSpotVisitHisPages(page, spotId);
    }

    @Override
    public DTO<Boolean> checkSpotExists(Location location, Integer distance) {
        Integer cnt = spotMapper.checkSpotExists(location.getLatitude(), location.getLongitude(), distance);
        return cnt > 0 ? DTO.success(Boolean.TRUE) : DTO.success(Boolean.FALSE);
    }

    @Override
    public SpotEnhanceDto getUpdateSpot(Long spotId) {
        return Optional.ofNullable(spotMapper.selectByIdForUpdate(spotId)).map(SpotEnhanceDto::format).orElse(null);
    }

    @Transactional
    @Override
    public void updateSpot(FishingSpotUpdateDto dto) {
        dto.toJson();
        dto.setUpdatedBy(CommonUtils.getUserId());
        spotMapper.updateSpot(dto);
    }

    @Override
    public List<SpotDashboardDto> selectSecretSpotsByLocationAndDistance(Location location, Integer distance) {
        Long userId = CommonUtils.getUserId();
        return spotMapper.selectSecretSpotsByLocationAndDistance(location.getLatitude(), location.getLongitude(), distance);
    }

    @Override
    public SpotHarvestDto getSpotHarvest(Long harvestId) {
        SpotHarvestDto dto = spotHavestMapper.getSpotHarvest(harvestId, CommonUtils.getUserId(), SpotHarvestDto.LIKE_TYPE);
        if (dto != null) {
            SpotHarvestDto.format(dto);
        }
        return dto;
    }


    @Override
    public Boolean harvestLikes(String type, Long targetId) {
        Boolean success = likeService.likes(type, targetId);
        spotMapper.updateHarvestLikeCnt(type, targetId);
        return success;
    }

    @Override
    public Boolean harvestDislike(String type, Long targetId) {
        Boolean success = likeService.dislike(type, targetId);
        spotMapper.updateHarvestLikeCnt(type, targetId);
        return success;
    }

    @Override
    public List<SearchResponseDto> search(String queryStr, String cityCode) {
        return spotMapper.search(queryStr,cityCode,CommonUtils.getUserId());
    }
    @Override
    public IPage<SpotListDto> listSpots(Integer pageNum, Integer pageSize, Boolean isFree, String cityCode, Boolean isPrivate, Boolean isForbidden, String query) {
        if (pageNum < 1) pageNum = 1;
        if (pageSize < 1 || pageSize > 100) pageSize = 20;
        
        IPage<SpotListDto> page = new Page<>(pageNum, pageSize);
        IPage<SpotListDto> pageData = spotMapper.selectSpotListPage(page, isFree, cityCode, isPrivate, isForbidden, query);
        pageData.setRecords(pageData.getRecords().stream().map(SpotListDto::format).toList());
        return pageData;
    }

    @Override
    public void toggleSpotStatus(Long spotId, Boolean disabled) {
        int updatedRows = spotMapper.updateDisabledStatus(spotId, disabled);
        if (updatedRows == 0) {
            throw new RuntimeException("钓点不存在");
        }
    }


}

